(function (window, document, undefined) {

	var CasualKitSDK = (function (window, document, undefined) {
		var 
			sdk = {},
			VERSION = '1.0.3',
			_callID = 1,
			_hash = '',
			_accessToken = null,
			_tokenExpiry = null,
			_gameId = null,
			_state = null,
			_redirectUrl = null,
			_apiUrl = 'https://api.hojio.com',
			_paymentUrl = 'https://payments.hojio.com',
			_authUrl = 'https://api.hojio.com/authorize',
			_debug = false,
			_orderKey = null,
			_ticket = null
			;
			
	// ** Public functions ** //
		/**
		 * Redirect the opener window to a page and add the hash values.
		 * Opener can only be redirected when on same domain as opened window (redirect url).
		 * @param {string} url Url
		 */
		sdk.redirectOpener = function( uri ) {
			uri = uri || window.opener.document.location.pathname;
 			window.opener.document.location.href = uri + location.hash;
			window.opener.focus();
			window.close();
		};
		
		/**
		 * Config sdk instance with game info
		 * @param {object} cfg Config object
		 * @example {gameId: 123, redirectUrl: 'http://myredir.url/my/game', state: optionalstring}
		 */
		sdk.config = function( cfg ) {
			if( typeof cfg.gameId !== 'undefined' ) { sdk.setGameId(cfg.gameId); }
			if( typeof cfg.redirectUrl !== 'undefined' ) { sdk.setRedirectUrl(cfg.redirectUrl); }
			if( typeof cfg.state !== 'undefined' ) { sdk.setState(cfg.state); }
			if( typeof cfg.debug !== 'undefined' ) { _debug = cfg.debug; }
		};

		/**
	 	 * Connect to the Api to get an access token to use for api requests
	 	 * @param {function} callback function callback(connected:boolean, err:string);
		 * @returns {Boolean}
		 */
		sdk.connect = function( callback ) {
			if( !sdk.getGameId() || !sdk.getRedirectUrl() ) {
				return error('gameId or redirectUrl missing', callback);
			}
			var oAuthUrl = _authUrl 
				+ '?response_type=token'
				+ '&client_id=' + sdk.getGameId()
				+ '&state=' + sdk.getState(true)
				+ '&redirect_url=' + sdk.getRedirectUrl() 
				+ '&scope=basic';

			if( hashGetToken(document.location.hash, callback) ) {
				// Token already in hash
				return true;
			}
			listen( callback );
			var width = 300, height = 500;
			
			debug('open url', oAuthUrl);

			window.open(oAuthUrl, 'casualkitlogin', 'width=' + width + ',height=' + height + ',toolbar=no,scrollbars=no' );
			return true;
		};
		
		/**
		 * Get game id (set either with config() or setGameId())
		 * @returns {number|null} Game id
		 */
		sdk.getGameId = function( ) {
			return _gameId;
		};

		/**
		 * Set Game id for connection
		 * @param {number} gameId
		 */
		sdk.setGameId = function( gameId ) {
			_gameId = gameId;
		};

		/**
		 * Get the access token. Will be set by connection or with setAccessToken()
		 * @returns {string} Access token
		 */
		sdk.getAccessToken = function() {
			return _accessToken;
		};

		/**
		 * Set the access token
		 * @param {string|null} token
		 */
		sdk.setAccessToken = function( token ) {
			_accessToken = token;
		};

		/**
		 * Get token expiry. Will be set by connection or with setTokenExpiry()
		 * @returns {number|null}
		 */
		sdk.getTokenExpiry = function() {
			return _tokenExpiry;
		};
		
		/**
		 * Set the token expiry as a timestamp in seconds.
		 * @param {number} secs The expiry timestamp in seconds or seconds until expiry ( if seconds until expiry, set addToNow = true )
		 * @param {boolean} addToNow (optional) add current time to secs.
		 */
		sdk.setTokenExpiry = function( secs, addToNow ) {
			_tokenExpiry = Number(secs);
			if( addToNow ) {
				_tokenExpiry += Math.floor( new Date().getTime() / 1000 );
			}
		};

		/**
		 * Check if the token is valid or has expired.
		 * @param {number} expiry (optional) Timestamp to check, if not set checks against currently set token expiry.
		 * @returns {Boolean} true = valid
		 */
		sdk.checkTokenExpiry = function( expiry ) {
			expiry = expiry || sdk.getTokenExpiry();
			var now = new Date().getTime() / 1000;
			return timestamp > now;
		};

		/**
		 * Create a new random state string.
		 * @param {string} prefix (optional) A prefix can be added to be able to recognize the state.
		 * @returns {string} state
		 */
		sdk.generateState = function( prefix ) {
			prefix = prefix || '';
			var uuid = getUUID();
			return prefix + uuid;
		};

		/**
		 * Get the state. Option to generate a new one if it doesnt exist
		 * @param {boolean} generate Force it to generate a new state if none exists
		 * @returns {string} state string
		 */	
		sdk.getState = function( generate ) {
			if( !_state && generate ) {
				sdk.setState( sdk.generateState() );
			}
			return _state;
		};
		
		/**
		 * Set the state
		 * @param {string} state
		 */
		sdk.setState = function( state ) {
			_state = state;
		};

		/**
		 * Get the redirect url. Is set in config or setRedirectUrl
		 * @returns {string} Redirect url
		 */
		sdk.getRedirectUrl = function( ) {
			return _redirectUrl;
		};

		/**
		 * Set the redirect url
		 * @param {string} url
		 */
		sdk.setRedirectUrl = function( url ) {
			_redirectUrl = url;
		};
/*
 * Data Kit
 */
		/**
		 * Get player info
		 * @category Data Kit 
	 	 * @param {function} callback function({object|null} data, {string|null} error, {string} uri)
		 */
		sdk.getPlayer = function( callback ) {
			var uri = '/player';
			jsonpGetData( uri, null, callback );
		};
		
		/**
		 * Get player game specific data.
		 * @category Data Kit 
		 * @param {Array} keys Array of keys to get. Will get all when empty
	 	 * @param {function} callback function({object|null} data, {string|null} error, {string} uri)
		 */
		sdk.getData = function( keys, callback ) {
			keys = keys || [];
			if( !(keys instanceof Array) ) {
				return error('keys must be an array: ' + toString.call(keys), callback);
			}
			var uri = '/player/data';
			jsonpGetData( uri, keys, callback );
		};
		
		/**
		 * Set player game specific data. 
		 * @category Data Kit 
		 * @param {object} Data object of {key: value}. Value may be an object as well.
	 	 * @param {function} callback function({object|null} data, {string|null} error, {string} uri)
		 */
		sdk.setData = function( data, callback ) {
			postData('/player/data', data, callback);
		};
/*
 * Payment Kit
 */
		/**
	 	 * Get player products
		 * @category Payment Kit 
		 * @param {array} keys
	 	 * @param {function} callback function({object|null} data, {string|null} error, {string} uri)
		 */
		sdk.getProducts = function( keys, callback ) {
			keys = keys || [];
			if( !(keys instanceof Array) ) {
				return error('keys must be an array: ' + toString.call(keys), callback);
			}
			var uri = '/player/product';
			jsonpGetData( uri, keys, callback );
		};

		/**
	 	 * Use player products
		 * @category Payment Kit 
	 	 * @param {object} products Object of productKey: amount-to-use.
	 	 * @param {function} callback function({object|null} data, {string|null} error, {string} uri)
		 * Data will contain the products with the new values. It will contain {success: false, products: object}
		 */
		sdk.useProducts = function( products, callback ) {
			postData('/player/product/use', products, callback);
		};

		/**
	 	 * Get all the products available to the game
		 * @category Payment Kit 
	 	 * @param {function} callback function({object|null} data, {string|null} error, {string} uri)
		 */
		sdk.getGameProducts = function( callback ) {
			var uri = '/game/products';
			jsonpGetData( uri, null, callback );
		};

		/**
		 * Create payment and listen for completion of the order. This can be done manually with createPayment(), popPaymentWindow() and listenForPayment() functions.
		 * @category Payment Kit 
		 * @param {string} packageKey Product package key  - can be found on casualkit.com/publisher ('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')
	 	 * @param {function} callback function({object|null} data, {string|null} error, {string} uri)
		 */
		sdk.requestPayment = function( packageKey, callback ) {
			var win = sdk.popPaymentWindow( );
			sdk.createPayment(packageKey, function(data, err) {
				// listen to checkPayment
				var orderKey = data.orderKey;
				if( orderKey ) {
					_orderKey = data;
					sdk.popPaymentWindow(orderKey, win);
					sdk.listenForPayment( orderKey, callback);
				} else {
					error('invalid orderkey', callback);
				}
			});
		};
		
		/**
		 * Get last requested orderKey
		 * @return {string} orderKey or null 
		 */
		sdk.getOrderKey = function() {
			return _orderKey;	
		};
		/**
		 * Create a payment
		 * @category Payment Kit 
		 * @param {string} packageKey Product package key  - can be found on casualkit.com/publisher ('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')
	 	 * @param {function} callback function({object|null} data, {string|null} error, {string} uri)
		 * data object contains orderKey.
		 */
		sdk.createPayment = function( packageKey, callback ) {
			var uri = '/player/create-order/' + packageKey;
			jsonpGetData( uri, null, callback );
		};

		/**
		 * Get the order status. status:complete when payment has been completet by the player.
		 * @category Payment Kit 
		 * @param {string} orderKey Key of the order. Created with createPayment().
	 	 * @param {function} callback function({object|null} data, {string|null} error, {string} uri)
		 */
		sdk.checkPayment = function( orderKey, callback ) {
			var uri = '/player/check-order/' + orderKey;
			jsonpGetData( uri, null, callback );
		};

		/**
		 * Listen for payment completion. Calls checkPayment every second until status of the order has changed.
		 * @category Payment Kit 
		 * @param {string} orderKey Key of the order. Created with createPayment().
	 	 * @param {function} callback function({object|null} data, {string|null} error, {string} uri)
		 */
		sdk.listenForPayment = function( orderKey, callback ) {
			debug('listen: payment', orderKey);
			var statement = function(data) {
				// Check for cancelled here as well?
				return data.status === 'complete';
			};
			listenForData(sdk.checkPayment, [orderKey], statement, callback);
		};
		
		/**
		 * Open a new window with payment url, so player can pay for a created order
		 * @category Payment Kit 
		 * @param {string} orderKey Key of the order. Created with createPayment().
		 * @param {object} childWindow (optional) Use an existing window instead of opening a new one.
		 * @returns {object} Window object of the opened window.
		 */
		sdk.popPaymentWindow = function( orderKey, childWindow ) {
			var url = orderKey ? _paymentUrl + '/buy-product/' + orderKey : '';
			if( childWindow && childWindow.location ) {
				childWindow.location = url;
				return childWindow;
			}
			return popWindow(url);
		};
/*
 * Betting Kit
 */
		/**
		 * Get all the bet types available to the game.
		 * @category Betting Kit 
	 	 * @param {function} callback function({object|null} data, {string|null} error, {string} uri)
		 */
		sdk.getBetTypes = function( callback ) {
			var uri = '/game/bet-types';
			jsonpGetData( uri, null, callback );
		};

		/**
		 * Creates a bet ticket and then listens for the player to confirm the ticket before calling the callback function. 
		 * This can be done manually with createPlayerTicket(), popTicketWindow() and listForTicket() functions.
		 * @category Betting Kit 
		 * @param {string} typeKey The key of the bet type - found on casualkit.com/publisher ('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')
		 * @param {function} callback function({object|null} data, {string|null} error, {string} uri)
		 */
		sdk.requestPlayerTicket = function( typeKey, callback ) {
			var win = sdk.popTicketWindow( );
			sdk.createPlayerTicket(typeKey, function(data, err) {
				var ticket = data.ticket;
				if( ticket ) {
					_ticket = data;
					sdk.popTicketWindow(ticket, win);
					sdk.listenForTicket( ticket, callback);
				} else {
					error('invalid ticket', callback);
				}
			});
		};

		/**
		 * Get last requested ticket
		 * @return {object} orderKey or null 
		 */
		sdk.getTicket = function() {
			return _ticket;	
		};
		/**
		 * Create a bet ticket for the player to confirm, and for you to afterwards use with a bet instance.
		 * @category Betting Kit 
		 * @param {string} typeKey The key of the bet type - found on casualkit.com/publisher ('xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx')
	 	 * @param {function} callback function({object|null} data, {string|null} error, {string} uri)
		 */
		sdk.createPlayerTicket = function( typeKey, callback ) {
			var uri = '/player/create-ticket/' + typeKey;
			jsonpGetData( uri, null, callback );
		};

		/**
		 * Get player ticket status. status: confirmed, when the player has accepted the bet.
		 * @category Betting Kit 
		 * @param {string} ticket The ticket key retrieved from createPlayerTicket();
	 	 * @param {function} callback function({object|null} data, {string|null} error, {string} uri)
		 */
		sdk.checkPlayerTicket = function( ticket, callback ) {
			var uri = '/player/check-ticket/' + ticket;
			jsonpGetData( uri, null, callback );
			
		};
		
		/**
		 * Listen for payment completion. Calls checkPayment every second until status of the order has changed.
		 * @category Betting Kit 
		 * @param {string} ticket Ticket key string. Created with createPlayerTicket().
	 	 * @param {function} callback function({object|null} data, {string|null} error, {string} uri)
		 */
		sdk.listenForTicket = function( ticket, callback ) {
			debug('listen: ticket',ticket);
			var statement = function(data) {
				// Check for cancelled here as well?
				return data.status === 'confirmed';
			};
//			listenForData(sdk.checkPlayerTicket, [ticket], statement, callback);
			listenForData(sdk.checkPlayerTicket, [ticket], statement, function(data, err, raw) {
				if( data ) {
					data.ticket = ticket;
				}
				if( callback ) callback(data, err, raw);
			});
		};
		
		/**
		 * Open a new window with bet ticket url, so player can confirm the ticket.
		 * @category Betting Kit 
		 * @param {string} ticket Ticket key string. Created with createPlayerTicket().
		 * @param {object} childWindow (optional) Use an existing window instead of opening a new one.
		 * @returns {object} Window object of the opened window.
		 */
		sdk.popTicketWindow = function( ticket, childWindow ) {
			var url = ticket ? _paymentUrl + '/bet-ticket/' + ticket : '';
			if( childWindow && childWindow.location ) {
				childWindow.location = url;
				return childWindow;
			}
			return popWindow(url);
		};
		
		/**
		 * Get a bet with status, possibly result, and players 
		 * @param {type} betKey
		 * @param {type} callback
		 * @returns {undefined}
		 */
		sdk.getBet = function( betKey, callback ) {
			var uri = '/game/get-bet/' + betKey;
			jsonpGetData( uri, null, callback );
		};

	// ** Private functions ** //
		function popWindow( url ) {
			var 
				width = 500,
				height = 600
				;
			return window.open(url, 'casualkit' + getUUID(), 'width=' + width +',height=' + height + ',toolbar=no,scrollbars=no');
		}
		function listenForData( fn, fnArg, fnStatement, callback ) {
			debug('listen');
			fnArg = fnArg || [];
			var fnListen = function(data) {
				if( fnStatement(data) ) {
					if( callback && callback.call ) {
						callback.call(this, data, null, null);
					}
				} else {
					run();
				}
			};
			fnArg.push(fnListen);
			var maxRequests = 300;
			var time = 1000;
			var request = 0;
			run();
			/*
			 * Loop function
			 */
			function run() {
				debug('run');
				request++;
				if( request > maxRequests ) {
					// error?
					error('checked for 5 minutes, and nothing happened', callback);
					return;
				}
				setTimeout(function() {
					debug(fnArg);
					fn.apply(this, fnArg);
				}, time);
			}
		}
		function listen( callback ) {
			document.location.hash = '';
			hashListenToken(callback);
		}

		function hashListenToken(callback) {
			if( _hash !== document.location.hash ) {
				_hash = document.location.hash;
				if( hashGetToken( _hash, callback ) ) {
					return true;
				}
			}
			setTimeout(function(){
				hashListenToken(callback);
			},50);
		}

		/**
		 * Get access_token and expiry from hash and make
		 * @param {type} hash
		 * @param {type} callback
		 * @returns {Boolean}
		 */
		function hashGetToken(hash, callback) {
			var h = querystring.parse(hash.substr(1));
			if( h.access_token ) {
				sdk.setAccessToken( h.access_token );
				sdk.setTokenExpiry( h.expires_in, true );
				_hash = '';
				document.location.hash = _hash;
				debug('Token found', sdk.getAccessToken());
				if( callback && callback.call ) {
					callback.call(this, sdk.getAccessToken(), sdk.getTokenExpiry() );
				}
				return true;
			}
			return false;
		}

		/**
		 * Make jsonp request
		 * @param {string} uri
		 * @param {array} keys
		 * @param {function} callback
		 */
		function jsonpGetData( uri, keys, callback ) {
			/*
			 * getData should work, so why use jsonp??
			 */
			return getData( uri, keys, callback);


			if( !sdk.getAccessToken() ) {
				return error('invalid access token. Have you connected yet?', callback);
			}
			var callbackName = 'casualkit' + (Math.random() * 100).toString().replace(/\./g,'');
			// Remote domain from where we need to request data
			var src = _apiUrl
				+ uri
		 		+ '?access_token=' + _accessToken
				+ '&game_id=' + _gameId
				+ getKeysQueryString( keys )
			// Callback and callID are explained later
				+ '&callback=' + callbackName
				+ '&callID=' + _callID
			// data type jsonp to get a function call back
				+ '&datatype=jsonp'
				;
			// Global function
			window[callbackName] = function( callId, data ) {
				var err = null;
				var script = document.getElementById('casualkitjsonpscript_' + callId);
				if(script && script.parentNode) {
					script.parentNode.removeChild(script);
				}
				if( callback && callback.call ) {
					callback.call(this, data, err, src);
				}
				delete window[callbackName];
			};
			var head = document.getElementsByTagName('head')[0];
			var script = document.createElement('script');
			script.async = true;
			script.id = 'casualkitjsonpscript_' + _callID;
			script.type = 'text/javascript';
			script.src = src;

			// Appending the script to the head makes the request!
			head.appendChild(script);
			_callID++;
		};

		/**
		 * Make XMLHttpRequst with GET
		 * @param {string} uri
		 * @param {array} keys
		 * @param {function} callback
		 */
		function getData( uri, keys, callback ) {
			if( !sdk.getAccessToken() ) {
				return error('invalid access token. Have you connected yet?', callback);
			}
			var url = _apiUrl
				+ uri
		 		+ '?access_token=' + _accessToken
				+ '&game_id=' + _gameId
				+ getKeysQueryString( keys )
				+ '&datatype=json'
				;
			var xmlHttp = new XMLHttpRequest();
			xmlHttp.onreadystatechange = function() {
				onReadyStateChange( url, xmlHttp, callback);
			};
			xmlHttp.open( 'GET', url, true );
			xmlHttp.send();

			debug('Get data', url);
		}

		/**
		 * Make XMLHttpRequst with POST
		 * @param {string} uri
		 * @param {object} postdata
		 * @param {function} callback
		 */
		function postData( uri, postdata, callback ) {
			if( !sdk.getAccessToken() ) {
				return error('invalid access token. Have you connected yet?', callback);
			}
			var xmlHttp = new XMLHttpRequest();
			xmlHttp.onreadystatechange = function() {
				onReadyStateChange( url, xmlHttp, callback);
			};
			var url = _apiUrl
				+ uri
				+ '?access_token=' + _accessToken
				+ '&game_id=' + _gameId
				+ '&datatype=json'
				;
			xmlHttp.open( 'POST', url, true );
			xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
			var data = _urlencode( postdata );
			xmlHttp.send(data);
			
			debug('Post data', url, data);
		}

		/**
		 * Create and return the querystring of keys  
		 * @param {array} keys The array of keys
		 * @returns {String} the querystring to append, empty if !keys or empty
		 */
		function getKeysQueryString( keys ) {
			if( keys ) {
				if( !(keys instanceof Array) ) {
					return error('keys must be an array: ' + toString.call(keys), callback);
				}
				for(var i in keys) {
					keys[i] = ('' + keys[i]).replace(/[^a-z0-9-_]/ig, '');
				}
				return '&keys=' + keys.join(',');
			}
			return '';
		}

		/**
		 * XMLHttpRequest onreadystatechange function
		 * @param {string} url Url of the request (used for callback)
		 * @param {object} xmlHttp XMLHttpRequest object
		 * @param {function} callback
		 */
		function onReadyStateChange( url, xmlHttp, callback ) {
			if( xmlHttp.readyState === 4 ) {
				var err = null, data = null, txt = null;
				if( xmlHttp.status === 200 ) {
					txt = xmlHttp.responseText;
					try {
						data = _parseJSON( txt );
					} catch(e) {
						err = 'Error! Json parse Error, ' + xmlHttp.responseText;
					}
					debug('Request complete', data, txt);
				} else {
					err = 'Error! Status: ' + xmlHttp.status + ', ' + xmlHttp.statusText + ', ' + xmlHttp.responseText;
				}
				if( callback && callback.call ) {
					callback.call(this, data, err, url, txt);
				}
			}
		}
		function _urlencode( data ) {
			var arr = toArrays( data );
			var str = '';
			for(var i in arr) {
				var a = arr[i];
				str += a[0];
				if(a.length>1) {
					if(a.length>2) {
						str += '[' + a.slice(1,a.length-1).join('][') + ']';
					}
					str += '=' + a[a.length-1];
				}
				str += '&';
			}
			return str.substring(0,str.length-1);	
			function toArrays( data ) {
				var array = [];
				for(var i in data) {
					var a = data[i];
					if( typeof a == 'object' ) {
						var ar = toArrays(a);
						for( var k in ar ) {
							array.push([i].concat(ar[k]));
						}
					} else {
						array.push([i,a]);
					}
				}
				return array;
			}
		}
		// Convert json-string to object
		function _parseJSON(jsonStr) {
			if (typeof jsonStr !== "string" || !jsonStr) {
				return null;
			}

			// Has JSON.parse built in?
			if (window.JSON && window.JSON.parse) {
				return window.JSON.parse(jsonStr);
			}

			// Logic borrowed from https://github.com/douglascrockford/JSON-js/blob/master/json2.js
			if (/^[\],:{}\s]*$/
			.test(jsonStr.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
			.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
			.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
				return (new Function( 'return ' + jsonStr))();
			}
			throw new SyntaxError('JSON.parse');
		}
		function getUUID() {
			var uuid = String(Math.floor(Number(String(new Date().getTime()).substring(0, 10)) * Math.random()));
			return uuid;
		}
		var querystring = {
			stringify: function( params ) {
				var data = [];
				for(var i in params ) {
					data.push( encodeURI(i.toString()) + '=' + encodeURI(params[i].toString()) );
				}
				return data.join('&');
			},
			parse: function( qs ) {
				var obj = {};
				var q = qs.split('&');
				for(var i in q) {
					var qq = q[i].split('=');
					if( qq[0] ) {
						obj[qq[0]] = qq[1];
					}
				}
				return obj;
			},
			encode: function( data ) {
				return _urlencode( data );
			},
		};
		// Make public
		sdk.querystring = querystring;

		function error(str, callback) {
			throw new Error(str);
			if( callback && callback.call ) {
				callback.call(this, null, str);
			}
		}
		
		function debug() {
			if( _debug ) {
				console.log.apply(console, arguments);
			}
		}

	// ** Global function ** //
	/// Maybe create one for each call? - something with an id on instead
		return sdk;
	})(window, document);

	// We make 'how_awesome' a global variable
	window.CasualKitSDK = CasualKitSDK;
})(window, document);
